import _ from 'lodash'
import {toRaw} from "vue"
import {RouteLocationNormalizedLoaded} from "vue-router"
import {getGlobalVariableByName, getLayoutOutline} from "./runtime/CmpStore"

export const SIMPLE_RE = /^(?:\{(?:(\d+)|([a-z_][\w\.]*))\})$/i

/**
 * 将
 * inbound/Page1
 * outbound/Page1
 *
 * 转换为如下结构
 * [
 * { "type": "Dir", "path": "inbound", "absolutePath": "C:/Projects/wms85/wms/wms-ui-pc/src/pages/inbound", "leaf": false,
 *   "children": [
 *         {
 *             "type": "Module",
 *             "path": "/inbound/Page1",
 *             "absolutePath": "C:/Projects/wms85/wms/wms-ui-pc/src/pages/inbound",
 *             "leaf": true,
 *             "text": "Page1",
 *             "id": "$api$ApiConfig"
 *         }
 *   ]
 * }
 * ]
 */
export function buildFileTreeData(absPrefix: string, designJsList: string[]): any[] {
    const root = {}

    // 第一步，吧 inbound/a/b/Page1 变成 root 对象 {inbound: { a:{ b:{ page1:'' } } }}
    for (const designJs of designJsList) {
        // 吧 /a/b/c.design.js 变成 a.b.c
        let target = designJs
        if (_.endsWith(target, '.design.js')) {
            target = target.substring(0, target.length - '.design.js'.length)
        }
        const pathJoined = _.split(target, "/").join('.')
        _.set(root, pathJoined, '')
    }

    // 第二步, 把 root 对象转换成树结构
    const rootChildren: any[] = []
    buildFileTreeDataDeep(absPrefix, rootChildren, [], root)

    return rootChildren
}

export function buildFileTreeDataDeep(absPrefix, treeData: any[], travPaths: string[], parent: any) {
    _.forOwn(parent, (value, key) => {
        if (typeof value === 'string') {
            // 这是一个 Module 节点
            const item = {
                type: 'Module',
                path: _.join([...travPaths, key], '/'),
                absolutePath: value,
                leaf: true,
                text: key,
                id: _.join([...travPaths, key], '$')
            }
            treeData.push(item)

        } else if (typeof value === 'object') {
            // 这是一个 Dir 节点
            const item = {
                type: 'Dir',
                id: _.join([...travPaths, key], '$'),
                path: _.join([...travPaths, key], '/'),
                absolutePath: _.join([absPrefix, ...travPaths, key], '/'),
                leaf: false,
                text: key,
                children: []
            }
            treeData.push(item)
            buildFileTreeDataDeep(absPrefix, item.children, [...travPaths, key], value)
        }
    })
}

/**
 * 获取路由的最末尾路由的名称
 */
export function getSelectedTopMenu(route: RouteLocationNormalizedLoaded) {
    const matched = route.matched;
    if (matched?.length < 2) return;
    const {name} = matched[1];
    return name;
}

/**
 * 解析控件中的 ctl.bind[property], 如果有绑定对象，尝试获取到值.
 * 如果 ctl.bind[property] 写的是 {a.b} 就从 vcxt.scope.a.b 中获取
 * 如果 ctl.bind[property] 写的是 a.b 就从 vcxt.scope.a.b 中获取
 * 如果 ctl.bind[property] 写的是 $row.a.b 就从 scope-dialog.$row.a.b 中获取
 * @param ctl 子控件
 * @param property bind 中的属性, 比如 value 代表取 ctl.bind.value
 */
export function evalGetBind(ctl, property) {
    if (!ctl.bind) {
        return
    }
    let bindValue = ctl.bind[property]
    if (!bindValue) {
        return
    }
    if (_.startsWith(bindValue, '{') && _.endsWith(bindValue, '}')) {
        bindValue = bindValue.substring(1, bindValue.length - 1)
    }
    if (!bindValue) {
        return
    }

    if (_.startsWith(bindValue, '$row.')) {
        bindValue = bindValue.substring(5)
        return _.get(ctl.dialogScope, 'row.' + bindValue)
    }
    return _.get(ctl.vcxt.scope, bindValue)
}

/**
 * 解析控件中的 ctl.bind[property], 如果有绑定表达式，尝试写入值
 * 如果 ctl.bind[property] 写的是 {a.b} 就写入 ctl.vcxt.scope.a.b
 * 如果 ctl.bind[property] 写的是 a.b 就写入 ctl.vcxt.scope.a.b
 * 如果 ctl.bind[property] 写的是 $row.a.b 就写入 ctl.scope-dialog.row.a.b
 * @param ctl 子控件
 * @param property bind 中的属性, 比如 value 代表写入 ctl.bind.value 描述的目标中
 * @param value 写入的值
 */
export function evalSetBind(ctl, property, value) {
    if (!ctl.bind) {
        return
    }
    let bindValue = ctl.bind[property]
    if (!bindValue) {
        return
    }
    if (_.startsWith(bindValue, '{') && _.endsWith(bindValue, '}')) {
        bindValue = bindValue.substring(1, bindValue.length - 1)
    }
    if (!bindValue) {
        return
    }

    if (_.startsWith(bindValue, '$row.')) {
        bindValue = bindValue.substring(5)
        _.set(ctl.dialogScope, 'row.' + bindValue, value)
        return
    }
    _.set(ctl.vcxt.scope, bindValue, value)
}

/**
 * 从引用名和xtype类型，深度递归查找到目标控件
 */
export function findVjsonByReference(vjson, xtype, reference) {
    let result = undefined
    let findCount = 0

    const tryFindDesignVjson = (designVJson) => {
        deepTrav(designVJson, (target) => {
            // xtype 相同, 且 reference 相同, 且只有唯一的一条记录
            if (target.xtype === xtype && target.reference === reference) {
                result = target
                findCount++
            }
        })
    }

    // 根据布局处理内容
    const designerVjson = vjson
    const define = getLayoutOutline(designerVjson)
    if (!define) {
        console.error('没有找到布局定义', designerVjson)
    } else {
        _.forOwn(define, (define, propName) => {
            if (define.isList === true) {
                _.forEach(designerVjson[propName], item => {
                    tryFindDesignVjson(item)
                })

            } else {
                tryFindDesignVjson(designerVjson[propName])
            }
        })
    }

    if (findCount === 1) {
        return result
    } else if (findCount === 0) {
        system.msg('找到多个匹配的组件，无法确定')
        return
    }
    system.msg('没有找到对应组件的设计时')
}

/**
 * 判断当前是否处于设计时状态
 */
export function isPreviewMode() {
    return !!window['is_preview_mode'] && !!window['designer']
}

/**
 * 尝试根据运行时 vjson 获取到设计时的 vjson 本体
 * 一般用来根据运行时状态，设定设计时的某些属性
 */
export function tryGetDesignVjsonByRuntime(runtimeVjson, designer) {
    let result = undefined
    let findCount = 0

    const tryFindDesignVjson = (designVJson) => {
        deepTrav(designVJson, (target) => {
            // xtype 相同, 且 reference 相同, 且只有唯一的一条记录
            if (target.xtype === runtimeVjson.xtype &&
                (!runtimeVjson.reference || target.reference === runtimeVjson.reference)) {

                if (target.xtype === 'aggrid') {
                    // target.columns
                    // 获取 columns 中每个字段的 dataIndex
                    const targetColumnJoin = _.map(target.columns, column => column.dataIndex).join(',')
                    const runtimeVjsonColumnJoin = _.map(runtimeVjson.columns, column => column.dataIndex).join(',')
                    if (targetColumnJoin !== runtimeVjsonColumnJoin) {
                        return
                    }
                }

                result = target
                findCount++
            }
        })
    }

    // 根据布局处理内容
    const designerVjson = designer.vjson
    const define = getLayoutOutline(designerVjson)
    if (!define) {
        console.error('没有找到布局定义', designerVjson)
    } else {
        _.forOwn(define, (define, propName) => {
            if (define.isList === true) {
                _.forEach(designerVjson[propName], item => {
                    tryFindDesignVjson(item)
                })

            } else {
                tryFindDesignVjson(designerVjson[propName])
            }
        })
    }

    if (findCount === 1) {
        return result
    } else if (findCount === 0) {
        system.msg('找到多个匹配的组件，无法确定')
        return
    }
    system.msg('没有找到对应组件的设计时')
}

function conversion_getDPI() {
    //@ts-ignore
    if (window.screen.deviceXDPI) {
        //@ts-ignore
        dpix = window.screen.deviceXDPI;
        //@ts-ignore
        dpiy = window.screen.deviceYDPI;
    } else {
        const tmpNode = document.createElement("div");
        tmpNode.style.cssText = "width:1in;height:1in;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden";
        document.body.appendChild(tmpNode);
        //@ts-ignore
        dpix = parseInt(tmpNode.offsetWidth);
        //@ts-ignore
        dpiy = parseInt(tmpNode.offsetHeight);
        tmpNode.parentNode.removeChild(tmpNode);
    }
}

export function calcStyle(vjson): any {
    const style: any = {}
    if (vjson.heightMode === 'fix') {
        style.height = vjson.height
    }
    if (vjson.marginTop) {
        style.marginTop = vjson.marginTop + 'px'
    }
    if (vjson.marginRight) {
        style.marginRight = vjson.marginRight + 'px'
    }
    if (vjson.marginBottom) {
        style.marginBottom = vjson.marginBottom + 'px'
    }
    if (vjson.marginLeft) {
        style.marginLeft = vjson.marginLeft + 'px'
    }
    if (vjson.paddingTop) {
        style.paddingTop = vjson.paddingTop + 'px'
    }
    if (vjson.paddingRight) {
        style.paddingRight = vjson.paddingRight + 'px'
    }
    if (vjson.paddingBottom) {
        style.paddingBottom = vjson.paddingBottom + 'px'
    }
    if (vjson.paddingLeft) {
        style.paddingLeft = vjson.paddingLeft + 'px'
    }
    if (vjson.cssBackgroundColor) {
        style.backgroundColor = vjson.cssBackgroundColor
    }
    if (vjson.cssColor) {
        if (vjson.xtype === 'staticfield') {

        } else {
            style.color = vjson.cssColor
        }

    }
    if (vjson.isBlod) {
        style.fontWeight = 'bold'
    }
    if (vjson.cssFontSize) {
        style.fontSize = vjson.cssFontSize
    }
    if (vjson.textAlign) {
        style.textAlign = vjson.textAlign
    }
    if (vjson.isBorder) {
        style.borderColor = '#dcdfe6'
        style.borderStyle = 'solid'
        style.borderWidth = vjson.isBorder[0] + ' ' + vjson.isBorder[1] + ' ' + vjson.isBorder[2] + ' ' + vjson.isBorder[3]
    }
    return style
}

export function calcStyleLabel(vjson): any {//label风格
    const style: any = {}
    if (vjson.labelBackgroundColor) {
        style.backgroundColor = vjson.labelBackgroundColor
        style.padding = '0 5px'
    }
    if (vjson.cssColor) {
        style.color = vjson.cssColor
    }
    if (vjson.isBlod) {
        style.fontWeight = 'bold'
    }
    if (vjson.cssFontSize) {
        style.fontSize = vjson.cssFontSize
    }
    return style
}

export function calcStyleContent(vjson): any {//内容风格
    const style: any = {}
    if (vjson.textCssColor) {
        style.color = vjson.textCssColor
    }
    if (vjson.textCssBgColor) {
        style.backgroundColor = vjson.textCssBgColor
        style.padding = '0 5px'
    }
    if (vjson.isBlod) {
        style.fontWeight = 'bold'
    }
    if (vjson.cssFontSize) {
        style.fontSize = vjson.textCssFont
    }
    return style
}

export function calcStyleAndWidth(vjson) {
    const style = calcStyle(vjson)
    if (vjson.width) {
        style.width = vjson.width
    }
    if (vjson.minHeight) {
        style.minHeight = vjson.minHeight
    }
    if (vjson.maxHeight) {
        style.maxHeight = vjson.maxHeight
    }
    if (vjson.isBorder) {
        style.borderColor = '#dcdfe6'
        style.borderStyle = 'solid'
        style.borderWidth = vjson.isBorder[0] + ' ' + vjson.isBorder[1] + ' ' + vjson.isBorder[2] + ' ' + vjson.isBorder[3]
    }
    return style
}

export function calcClass(vjson) {
    const cls = []
    if (vjson.cls) {
        cls.push(vjson.cls)
    }
    if (vjson.heightMode === 'fit') {
        cls.push('fit-height')
    }
    if (vjson.isCard) {
        cls.push('yv-card')
    }
    return cls
}

/**
 * 获取某元素绝对位置
 */
export function getElementAbsPos(e) {
    let t = e.offsetTop;
    let l = e.offsetLeft;
    while (e = e.offsetParent) {
        t += e.offsetTop;
        l += e.offsetLeft;
    }
    return {left: l, top: t};
}

/**
 * 基于某个VueInstance, 动态运行某个表达式
 * @param funcBody 函数体
 * @param vueInstance 某个 vue 组件
 */
export function runExpress(funcBody: any, vueInstance: any) {
    const me = vueInstance.vcxt.scope
    const keys = []
    const values = []
    // _.forOwn(me, (v, k) => {
    //     keys.push(k)
    //     values.push(v)
    // })
    //
    // _.forOwn(me.$data, (v, k) => {
    //     keys.push(k)
    //     values.push(v)
    // })

    const func = Function(...keys, funcBody)
    try {
        return func.bind(vueInstance.vcxt.scope)(...values)
    } catch (e) {
        console.error('runExpress', e)
        //@ts-ignore
        system.showErrorDialog(e.toString())
        return () => {
        }
    }
}

/**
 * 从字符串，获取带方法体的JS对象。
 * 比如根据 design.js 的文本内容，提取出 object 并且内部带有 function 定义
 */
export function parseJsObject(strContent): any {
    strContent = _.trim(strContent)
    if (_.startsWith(strContent, 'export default')) {
        strContent = strContent.substring('export default'.length)
    }
    return eval('(' + strContent + ')')
}

/**
 * 运行事件
 * @param scope vcxt.scope
 * @param vjson VJSON,是指能包含 listeners 属性的对象
 * @param eventName 事件名
 * @param args 事件参数
 */
export function runEvent(scope, vjson, eventName, ...args) {
    if (!vjson.listeners) {
        return
    }
    if (typeof vjson.listeners[eventName] === 'string') {
        let funcBody = vjson.listeners[eventName]
        const me = scope
        const methodVarName = []

        // // 吧 args 参数添加进去
        _.forEach(args, (v, k) => {
            methodVarName.push("arg" + k)
        })

        // 花括号定义模式
        if (_.startsWith(funcBody, "{")) {
            if (_.endsWith(funcBody, "}")) {
                // 剔除花括号
                const methodName = funcBody.substring(1, funcBody.length - 1)

                // 检查有没有其他符号
                if (methodName.includes('+') || methodName.includes('-') || methodName.includes('(') ||
                    methodName.includes(')') || methodName.includes('*') || methodName.includes('/') ||
                    methodName.includes('{') || methodName.includes('}') || methodName.includes(' ')) {
                    console.error('错误表达式:' + funcBody)
                    return
                }
                // funcBody = "return this." + funcBody + "(arguments);"
                try {
                    return me[methodName].call(me, ...args)
                } catch (e) {
                    console.error(e)
                    system.showErrorDialog('方法运行错误' + e)
                    return
                }

            } else {
                console.error('错误表达式:' + funcBody)
                return
            }
        }

        const func = Function(...methodVarName, funcBody)
        try {
            return func.bind(scope)(...args)
        } catch (e) {
            console.error('runEventError', e)
            //@ts-ignore
            system.showErrorDialog(e.toString())
            return
        }
    }
    if (typeof vjson.listeners[eventName] === 'function') {
        vjson.listeners[eventName].call(scope, this, ...args)
    }
}

export function deepEqual(obj1, obj2) {
    console.log('deepEqual', obj1, obj2)
    //其中一个为值类型或null
    if (!(typeof obj1 === 'object') || !(typeof obj2 === 'object')) {
        return obj1 === obj2;
    }

    //判断是否两个参数是同一个变量
    if (obj1 === obj2) {
        return true;
    }

    //判断keys数是否相等
    const obj1Keys = Object.keys(obj1);
    const obj2Keys = Object.keys(obj2);
    if (obj1Keys.length !== obj2Keys.length) {
        return false;
    }

    //深度比较每一个key
    for (let key in obj1) {
        if (!deepEqual(obj1[key], obj2[key])) {
            return false;
        }
    }

    return true;
}

/**
 * 去掉 VJson 中每一个对象的 ID
 */
export function removeVJsonId(vjson) {
    deepTrav(vjson, (obj) => {
        if (obj.hasOwnProperty('id')) {
            delete obj.id
        }
        if (obj.xtype === 'grid' || obj.xtype === 'yvgrid' || obj.xtype === 'aggrid' || obj.xtype === 'tree') {
            // 为每一个 grid.column 加上 id
            _.forEach(obj.columns, (c) => {
                if (c.hasOwnProperty('id')) {
                    delete c.id
                }
            })
        }
    });
    return vjson
}

/**
 * 为 VJson 每一个对象加上 ID
 */
export function addVJsonId(vjson) {
    deepTrav(vjson, (obj) => {
        if (typeof obj.xtype === 'string') {
            // 为每个带 xtype 属性的元素加上 id
            if (!obj.hasOwnProperty('id')) {
                obj.id = _.uniqueId('_id')
            }
        }
        if (obj.xtype === 'split') {
            // 为每个 split.items 属性的元素加上 id
            _.forEach(obj.items, item => {
                item.id = _.uniqueId('_id')
            })
        }
        if (obj.xtype === 'grid' || obj.xtype === 'yvgrid' || obj.xtype === 'aggrid' || obj.xtype === 'tree' || obj.xtype === 'yvtree') {
            // 为每一个 grid.column 加上 id
            _.forEach(obj.columns, (c) => {
                if (!c.hasOwnProperty('id')) {
                    c.id = _.uniqueId('_id')
                }
            })
        }
    });
    return vjson
}

/**
 * 分析 params 类似这样
 * {
 *     a: 'watch:{query.f1}:notNull',
 *     b: '{query.f2}',
 *     c: {
 *        c1: '{form1}',
 *        c2: 'watch:{form2}:notNull',
 *     }
 * }
 *
 * 运行Vue观察函数, 并主动回调
 * $watch('query.f1', (res)=>{},{deep:true})
 * $watch('form2', (res)=>{},{deep:true})
 *
 * 返回停止观察的函数
 *
 * @param $watch Vue.$watch函数
 * @param paramObject params描述
 * @param cb Watch回调函数
 */
export function analyzeParamsForWatch($watch, paramObject, cb): (() => void)[] {
    const stopHandles: (() => void)[] = []

    const watchIfExpress = (str) => {
        if (_.startsWith(str, "watch:") && str.includes('{') && str.includes('}')) {
            // '{foo}' 简单表达式
            const start = str.indexOf('{')
            const end = str.indexOf('}')

            const path = str.substring(start + 1, end);
            if (path.length > 0) {
                const watchStop = $watch(path, (newVal) => {
                    cb(path, newVal)
                }, {deep: true})
                // console.log('$watch ', path)
                stopHandles.push(watchStop)

                return '{' + path + '}'
            }
        }
    }

    const trav = (param) => {
        _.forOwn(param, (value, key) => {
            if (_.isPlainObject(value)) {
                // 深度递归，对子对象进行求解
                trav(value)

            } else if (_.isString(value)) {
                // 字符串直接用 calcExpress 表达式求解
                const r = watchIfExpress(param[key])
                // 以下代码注释掉，不要修改源参数，否则会导致表达式无法再次求解
                // if (typeof r === 'string' && r) {
                //     param[key] = r
                // }

            } else if (_.isArray(value)) {
                // 数组求解
                _.forEach(value, (v, idx) => {
                    if (_.isPlainObject(v)) {
                        // 深度递归，对子对象进行求解
                        trav(v)
                    } else if (_.isString(v)) {
                        const r = watchIfExpress(v)
                        // 以下代码注释掉，不要修改源参数，否则会导致表达式无法再次求解
                        // if (typeof r === 'string' && r) {
                        //     value[idx] = r
                        // }
                    }
                })
            }
        })
    }

    trav(paramObject)
    return stopHandles
}

/**
 * 为目标字符串每一行，加上一段前缀
 */
export function insertPrefixEachLine(target, prefix) {
    return _.join(_.map(target.split('\n'), line => {
        return prefix + line
    }), '\n')
}

export const addWindowResizeHandler = function (handler) {
    const oldHandler: any = window.onresize
    if (typeof window.onresize != 'function') {
        window.onresize = handler
    } else {
        window.onresize = function () {
            if (typeof oldHandler === 'function') {
                oldHandler()
            }
            handler()
        }
    }
}

export function getDefaultVExport(): any {
    return {
        props: {},
        data: {},
        computed: {},
        methods: {},
    }
}

/**
 * 深度遍历 vjson
 * @param obj 视图JSON
 * @param resolver 分析函数, 函数的第一个参数是被深度遍历对象，第二个参数是标记变量，如果标记变量中设定了 exitCallback 方法，退出当前遍历时会主动发生调用
 * @return 方法返回 false 时就不再进行深度遍历
 */
export function deepTrav(obj, resolver): undefined | boolean {
    const markObj: any = {}
    if (!obj) {
        return
    }
    if (_.isArray(obj)) {


    } else if (typeof obj === 'object') {
        const r = resolver(obj, markObj)
        if (r === false) {
            // 短路判断
            return false
        }
    }

    if (_.isArray(obj.items)) {
        let r
        _.forEach(obj.items, (item) => {
            r = deepTrav(item, resolver)
            return r
        })
        if (r === false) {
            // 短路判断
            return false
        }
    }

    if (typeof markObj?.exitCallback === 'function') {
        markObj.exitCallback()
    }
}

/**
 * 获取组件的拖拽类型
 */
export function getComponentDragGroup(vjson): 'rowGroup' | 'fieldGroup' {
    if (typeof vjson.xtype === 'string' && vjson.xtype) {

        const xtype: string = vjson.xtype
        if (xtype === 'button' || xtype === 'combo' || xtype === 'combogrid') {
            return 'fieldGroup'
        }
        if (xtype.includes('field')) {
            return 'fieldGroup'
        }
        if (xtype.includes('check')) {
            return 'fieldGroup'
        }
        if (xtype.includes('radio')) {
            return 'fieldGroup'
        }
        if (xtype.includes('gridcolumn')) {
            return 'fieldGroup'
        }

        return 'rowGroup'
    }
    console.error('can\'t get drag group', vjson)
}

/**
 * 获取Vue组件图标
 * 不要再用这个函数！ getIcon 已经失效
 */
// export function getIcon(icon): any {
//     if (!icon) {
//         return
//     }
//
//     if (_.startsWith(icon, 'x-fa')) {
//         return markRaw(() => {
//             return h('i', {class: icon})
//         })
//     } else {
//         const dd = ElementIcons[icon]
//         if (dd) {
//             // 来源于 elementUI
//             return markRaw(() => {
//                 return h(dd)
//             })
//         }
//     }
// }

export function getComponentName(vjson): string {
    /*
    const name = XTypeName[vjson.xtype]
    if (name) {
        return name
    }
     */
    return vjson.xtype ?? vjson.toString()
}

/**
 * 获取 vjson 组件定义中的事件
 */
export function getListener(vjson, eventName, vcxt) {
    const fn = _.get(vjson, 'listeners.' + eventName)
    if (typeof fn === 'function') {
        return fn
    }
    return () => {
    }
}

export function buildTreeData(data: Array<object>, config: {
    idField?: string,
    parentField?: string,
    checkField?: string,
    textField?: string,
    iconField?: string,
    checkStrictly?: boolean,
}, checkedIds: string[] = []): Array<{
    icon?: string,
    text?: string,
    children?: [],
    raw: object,
}> {
    if (!data) data = [];
    const allNode = []
    for (const item of data) {
        const node: any = {raw: item}
        if (config?.idField) node.id = item[config?.idField];
        if (config?.parentField) node.parent = item[config?.parentField];
        if (config?.textField) node.text = item[config?.textField];
        if (config?.iconField) node.icon = item[config?.iconField];
        if (config?.checkField) node.check = item[config?.checkField] ?? false;
        allNode.push(node)
    }
    const tree = []
    const buildTree = (node: any, isRoot: boolean) => {
        if (!node.id) return
        if (isRoot) tree.push(node)
        const children = allNode.filter(n => n.parent === node.id);
        // console.log("###", node.id, node.check, config.checkStrictly, children.length)
        if (node.check && (config.checkStrictly || children.length <= 0)) {
            checkedIds.push(node.id)
        }
        if (children.length <= 0) return
        node.children = children;
        for (let child of node.children) {
            buildTree(child, false)
        }
    };
    const rootNode = []
    for (let node of allNode) {
        if (!node.parent) {
            rootNode.push(node)
            break
        }
        const parentNode = allNode.find(n => node.parent === n.id)
        if (!parentNode) rootNode.push(node)
    }
    for (let root of rootNode) {
        buildTree(root, true)
    }
    return tree;
}


export function lookupScope(extHandle: any): any {
    if (!extHandle) {
        // @ts-ignore
        return
    }
    return extHandle.scope
}

export function lookupFn(vcxt: any, event: string, errorWho?: any): Function {
    if (isDesignMode(vcxt, undefined)) {
        return () => {
        }
    }

    if (!_.startsWith(event, '{') && !_.endsWith(event, '}')) {
        console.error('无法识别的事件响应类型', event)
        throw new TypeError('无法识别的事件响应类型')
    }

    const eventName = event.substring(1, event.length - 1)
    const funcResult = vcxt.scope[eventName]

    if (typeof funcResult !== 'function') {
        console.error('无法找到事件', event, vcxt, errorWho)
        // throw new TypeError('无法识别的事件响应类型')
        return () => {
        }
    }

    // if (!_.startsWith(event, 'scope.') &&
    //     !_.startsWith(event, 'system.') &&
    //     !_.startsWith(event, 'format.')) {
    //     console.error('无法识别的事件响应类型', event)
    //     throw new TypeError('无法识别的事件响应类型')
    // }
    //
    // // 为 eval 准备环境
    // const system = {}
    // const format = {} // window['yvanLib']
    //
    // const keys: any[] = []
    // const values: any[] = []
    // _.forOwn(scope, (value, key) => {
    //     const idxKey = key.indexOf('//')
    //     if (idxKey >= 0) {
    //         keys.push(key.substr(0, idxKey))
    //     } else {
    //         keys.push(key)
    //     }
    //     values.push(value)
    // })
    //
    // const func: Function = //eval(event)
    //     Function('scope', 'system', 'format', ...keys, 'return ' + event)
    //
    // const funcResult = func(scope, system, format, ...values)
    //
    // if (typeof funcResult !== 'function') {
    //     console.error('无法识别的事件响应类型', event, func, errorWho)
    //     throw new TypeError('无法识别的事件响应类型')
    // }

    return funcResult
}

export function isDesignMode(control: any, config: any) {
    return false
}

export function isEmpty(value: any): Boolean {
    if (typeof value === 'undefined' || value === null) {
        return true
    }
    if (typeof value === 'number' && isNaN(value)) {
        return true
    }
    if (value === 'N/A') {
        return true
    }
    return ("" + value).trim().length <= 0
}

export function toString(value: any): String {
    if (typeof value === 'undefined' || value === null) {
        return ""
    }
    if (typeof value === 'number' && isNaN(value)) {
        return ""
    }
    if (value === 'N/A') {
        return ""
    }
    return ("" + value)
}

export function createUUID(): String {
    //@ts-ignore
    return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
        (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
    );
}

/**
 * 尝试根据含表达式的对象回写, calcObjectFlat 的逆向方法
 * @example
 * tryWriteObject({ a:'{query.a}', b:{b1:'{query.b1}', b2:'{query.b2}'},c:'aa',d:['{query.d1}','{query.d2}']}, {a:'aValue', b:{b1:'b1Value', b2:'b2Value'}, c:'aa', d:[1,2]})
 *
 * expressObject:
 * {
 *     a: '{query.a}',
 *     b: {
 *         b1: '{query.b1}',
 *         b2: '{query.b2}',
 *     },
 *     c: 'aa',
 *     d: [
 *         '{query.a}',
 *         '{query.b2}'
 *     ]
 * }
 *
 * valueObject:
 * {
 *     a: 'aValue',
 *     b: {
 *         b1: 'b1Value',
 *         b2: 'b2Value'
 *     },
 *     c: 'aa'
 *     c: [
 *         'aValue',
 *         'b2Value'
 *     ]
 * }
 *
 * 系统会尝试回写
 * viewModel.set('query.a', 'aValue')
 * viewModel.set('query.b1', 'b1Value')
 * viewModel.set('query.b2', 'b2Value')
 *
 * @param expressObject 含表达式的对象
 * @param valueObject 表达式计算完成之后的结果对象
 * @param writeFn 写入的方法 (path, value)=>void
 */
export function tryWriteObject(expressObject, valueObject, writeFn) {

    const trav = (pathPrefix) => {
        let parent = expressObject
        if (_.size(pathPrefix) > 1) {
            parent = _.get(parent, pathPrefix.substring(1))
        }
        _.forOwn(parent, (value, key) => {
            if (_.isPlainObject(value)) {
                // 深度递归，对子对象进行求解
                trav(pathPrefix + "." + key)

            } else if (_.isString(value)) {
                // 字符串直接用 calcExpress 表达式求解
                if (SIMPLE_RE.test(value)) {
                    // If we have '{foo}' alone it is a literal 简单表达式
                    const targetPath = value.substring(1, value.length - 1);
                    const targetValue = _.get(valueObject, (pathPrefix + "." + key).substr(1))
                    if (!writeFn) {
                        console.log(`viewModel.set('${targetPath}', '${targetValue}')`)
                    } else {
                        writeFn(targetPath, targetValue)
                    }
                }
            } else if (_.isArray(value)) {
                _.each(value, (v, idx) => {
                    if (SIMPLE_RE.test(v)) {
                        const targetPath = (pathPrefix + "." + key).substr(1) + "[" + idx + "]";
                        const targetValue = _.get(valueObject, (pathPrefix + "." + key).substr(1) + "[" + idx + "]")
                        if (!writeFn) {
                            console.log(`viewModel.set('${targetPath}', '${targetValue}')`)
                        } else {
                            writeFn(targetPath, targetValue)
                        }
                    }
                })
            }
        })
    }

    trav("")
}

/**
 * 尝试去掉变量两边括号
 * {a} => a
 * a => a
 */
export function tryVarSimple(value) {
    if (SIMPLE_RE.test(value)) {
        // If we have '{foo}' alone it is a literal 简单表达式
        return value.substring(1, value.length - 1);
    }
    return value
}

/**
 * 对个对象进行表达式求值，不用回调
 * @example
 * calcObjectFlat({query:{a:'aValue',b1:'b1Value',b2:'b2Value',d1:1,d2:2}}, { a:'{query.a}', b:{b1:'{query.b1}', b2:'{query.b2}'},c:'aa',d:['{query.d1}','{query.d2}'] })
 *
 * {
 *     a: '{query.a}',
 *     b: {
 *         b1: '{query.b1}',
 *         b2: '{query.b2}',
 *     },
 *     c: 'aa',
 *     d: [
 *         '{query.d1}',
 *         '{query.d2}'
 *     ]
 * }
 *
 * 计算结果为
 * {
 *     a: 'aValue',
 *     b: {
 *         b1: 'b1Value',
 *         b2: 'b2Value'
 *     },
 *     c: 'aa'
 *     d: [
 *         '1',
 *         '2'
 *     ]
 * }
 *
 * @param data
 * @param paramObject
 * @param cb 回调函数 (express, value)=>newValue
 */
export function calcObjectFlat(model, paramObject, cb) {
    const result = _.cloneDeep(paramObject)

    if (typeof paramObject === 'string') {
        return calcExpress(model, paramObject)
    }

    const trav = (param) => {
        _.forOwn(param, (value, key) => {
            if (_.isPlainObject(value)) {
                // 深度递归，对子对象进行求解
                trav(value)

            } else if (_.isString(value)) {
                // 字符串直接用 calcExpress 表达式求解
                let newValue = calcExpress(model, param[key])
                if (cb) {
                    newValue = cb(param[key], newValue)
                }
                param[key] = newValue

            } else if (_.isArray(value)) {
                // 数组求解
                _.each(value, (v, idx) => {
                    let newValue = calcExpress(model, v)
                    if (cb) {
                        newValue = cb(param[key], newValue)
                    }
                    value[idx] = newValue
                })
            }
        })
    }

    trav(result)
    return result
}


/**
 * 对某个表达式进行求值
 * a:{query.a},b:{query.b} -> a:aValue,b:bValue
 *
 * @example
 * calcExpress(cc.viewModel.data, "WH_ID:{query.WH_ID},C:{theGrid.selection.data.WH_ID}")
 * 计算出来的值是: "WH_ID:queryWhId,C:JH000000001"
 *
 * @param data 数据环境对象
 * @param express 表达式对象
 */
export function calcExpress(data, express) {
    let result = express

    if (SIMPLE_RE.test(express)) {
        // '{foo}' 简单表达式
        const path = express.substring(1, express.length - 1);
        let ret = undefined
        if (path.startsWith('global.')) {
            const name = path.substring('global.'.length)
            const vd = getGlobalVariableByName(name)
            if (!vd) {
                console.error("can't found global define:" + name)
                return undefined
            }
            ret = vd.getValue()

        } else {
            ret = toRaw(_.get(data, path))
        }
        return ret
    }

    while (true) {
        const mlist = result.match(/{(.*?)}/)
        if (!mlist) {
            break
        }

        const pathC = mlist[0]  // {query.a}
        const path = mlist[1]  // query.a

        let value = _.get(data, path)
        result = result.replaceAll(pathC, _.toString(value))
    }

    return result
}

/**
 * 用于任意组件 Ext.Component 构造时，获取当前组件对应的表格（如果不是 grid.columns 对象就会返回 undefined）
 * @param config 组件构造函数传入的 config 配置文件
 */
export function getParentGrid(config) {
    return config.$initParent?.grid
}

/**
 * 通用 dataSource 解析，
 * Tree/Combo/Grid/PageList ... 都会从这里解析
 */
export function dataSourceReload(ctl: any, extraParam?: any, _successCb?: Function,
                                 successCallback?: (data: any[]) => void,
                                 failCallback?: (msg) => void): Promise<any> {
    const {config} = ctl

    return new Promise<any>((resolve, reject) => {
        if (!config.dataSource) {
            // 没有设置数据源，直接置空
            return Promise.resolve()
        }
        const option: DataSourceParam = {
            extraParam,
            _successCb,
            successCallback: (value) => {
                if (typeof successCallback === "function") {
                    successCallback(value)
                } else {
                    ctl.setData(value)
                }
                ctl.fireEvent('dataLoadComplete', ctl, true, value);
                ctl.setLoading(false)
                resolve(value)
            },
            failCallback: (error) => {
                if (typeof failCallback === "function") {
                    failCallback(error)
                }
                ctl.fireEvent('dataLoadComplete', ctl, false);
                ctl.setLoading(false)
                reject(error)
            }
        }

        // 请求前要求确认一下
        let isCanceled: boolean = false
        ctl.fireEvent('dataBeforeLoad', ctl, {
            cancel: () => {
                isCanceled = true
            },
            ...option
        });

        if (!isCanceled) {
            // 请求没有被取消
            // 调用异步接口返回 Promise
            return _innerReload(ctl, option)
        }
        return reject()
    })
}

function _innerReload(ctl: any, option: DataSourceParam) {
    const {config} = ctl
    ctl.setLoading(true)

    const scope = lookupScope(ctl)
    let dataSource = config.dataSource
    if (typeof dataSource == 'string') {
        dataSource = lookupFn(scope, config.dataSource)
    }

    if (typeof dataSource === 'function') {
        // @ts-ignore
        dataSource.call(scope, ctl, option)
        return

    } else if (typeof dataSource?.url === 'string' || typeof dataSource?.sqlId === 'string') {

        const params: any = {}
        let sortModel: any = null;
        let filterModel: any = null;
        if (option.extraParam && option.extraParam.filterModel) {
            filterModel = option.extraParam.filterModel;
        }
        if (option.extraParam && option.extraParam.sortModel) {
            sortModel = option.extraParam.sortModel;
        }

        _.forOwn(option.extraParam, (v: any, key) => {
            if (key != "filterModel" && key != "sortModel") {
                if (typeof v === 'function') {
                    params[key] = v()
                } else {
                    params[key] = v
                }
            }
        })
        const ds: DataSourceAjax<any> = dataSource

        _.forOwn(ds.params, (v: any, key) => {
            if (typeof v === 'function') {
                params[key] = v()
            } else {
                params[key] = v
            }
        })

        // if (typeof dataSource?.url === 'string') {
        //
        //     invokeServer(dataSource.url, {
        //         method: dataSource.method,
        //         url: ds.url,
        //         data: params,
        //         filterModel,
        //         sortModel,
        //     }).then(res => {
        //         if (res.success) {
        //             option.successCallback(res.data)
        //
        //         } else {
        //             option.failCallback(res.msg)
        //         }
        //
        //     }).catch(e => {
        //         option.failCallback(e)
        //
        //     })
        //
        // } else if (typeof dataSource?.sqlId === 'string') {
        //
        //     const ds: DataSourceSql<any> = ctl.dataSource
        //
        //     // @ts-ignore
        //     Extend.ajax.func({
        //         url: ds.sqlId,
        //         data: params,
        //         db: ds.db,
        //         method: 'sql',
        //         filterModel,
        //         sortModel,
        //     }).then(res => {
        //         if (res.success) {
        //             option.successCallback(res.data)
        //
        //         } else {
        //             option.failCallback(res.msg)
        //         }
        //
        //     }).catch(e => {
        //         option.failCallback(e)
        //
        //     }).finally(() => {
        //         ctl.loading = false
        //     })
        //
        //     return
        // } else {
        //     console.error("非法的 dataSource", ctl.dataSource)
        //     debugger
        //     throw new Error("非法的 dataSource")
        // }
    }
}

export interface DataSourceParam {
    /**
     * 额外附加的参数
     */
    extraParam?: any

    /**
     * 成功后额外的调用
     */
    _successCb?: Function

    /**
     * 设置响应成功的数据
     */
    successCallback: (data: any[]) => void

    /**
     * 设置响应失败
     * @param msg 错误消息（可以为空）
     */
    failCallback: (msg?: string) => void
}

export interface DataSourceBefore extends DataSourceParam {

    /**
     * 取消下一步的请求
     */
    cancel: () => void
}

/**
 * 方法作为数据源
 */
export type DataSourceFunction<T> = (sender: T, option: DataSourceParam) => void

/**
 * Ajax 数据源
 */
export interface DataSourceAjax<T> {
    url: string,
    method: string,
    params?: {
        [key: string]: (() => any) | any
    }
}

/**
 * Sql 数据源
 */
export interface DataSourceSql<T> {
    sqlId: string,
    db: string,
    params?: {
        [key: string]: (() => any) | any
    }
}

/**
 * 数据源配置
 */
export type DataSourceHelper<T> = DataSourceFunction<T> | DataSourceAjax<T> | DataSourceSql<T>

/**
 * 数据抽取之前的设置
 */
export type OnBeforeDataLoad<T> = (sender: T, option: DataSourceBefore) => void

/**
 * 数据加载完成之后的回调
 */
export type OnDataLoadComplete<T> = (sender: T) => void